import { forEach, find, matchPattern } from "min-dash";

import inherits from "inherits";

import CommandInterceptor from "diagram-js/lib/command/CommandInterceptor";

import { is } from "../../../util/ModelUtil";

export default function ReplaceConnectionBehavior(
  eventBus,
  modeling,
  bpmnRules,
  injector
) {
  CommandInterceptor.call(this, eventBus);

  var dragging = injector.get("dragging", false);

  function fixConnection(connection) {
    var source = connection.source,
      target = connection.target,
      parent = connection.parent;

    // do not do anything if connection
    // is already deleted (may happen due to other
    // behaviors plugged-in before)
    if (!parent) {
      return;
    }

    var replacementType, remove;

    /**
     * Check if incoming or outgoing connections
     * can stay or could be substituted with an
     * appropriate replacement.
     *
     * This holds true for SequenceFlow <> MessageFlow.
     */

    if (is(connection, "bpmn:SequenceFlow")) {
      if (!bpmnRules.canConnectSequenceFlow(source, target)) {
        remove = true;
      }

      if (bpmnRules.canConnectMessageFlow(source, target)) {
        replacementType = "bpmn:MessageFlow";
      }
    }

    // transform message flows into sequence flows, if possible

    if (is(connection, "bpmn:MessageFlow")) {
      if (!bpmnRules.canConnectMessageFlow(source, target)) {
        remove = true;
      }

      if (bpmnRules.canConnectSequenceFlow(source, target)) {
        replacementType = "bpmn:SequenceFlow";
      }
    }

    if (
      is(connection, "bpmn:Association") &&
      !bpmnRules.canConnectAssociation(source, target)
    ) {
      remove = true;
    }

    // remove invalid connection,
    // unless it has been removed already
    if (remove) {
      modeling.removeConnection(connection);
    }

    // replace SequenceFlow <> MessageFlow

    if (replacementType) {
      modeling.connect(source, target, {
        type: replacementType,
        waypoints: connection.waypoints.slice()
      });
    }
  }

  function replaceReconnectedConnection(event) {
    var context = event.context,
      connection = context.connection,
      source = context.newSource || connection.source,
      target = context.newTarget || connection.target,
      allowed,
      replacement;

    allowed = bpmnRules.canConnect(source, target);

    if (!allowed || allowed.type === connection.type) {
      return;
    }

    replacement = modeling.connect(source, target, {
      type: allowed.type,
      waypoints: connection.waypoints.slice()
    });

    // remove old connection
    modeling.removeConnection(connection);

    // replace connection in context to reconnect end/start
    context.connection = replacement;

    if (dragging) {
      cleanDraggingSelection(connection, replacement);
    }
  }

  // monkey-patch selection saved in dragging in order to re-select it when operation is finished
  function cleanDraggingSelection(oldConnection, newConnection) {
    var context = dragging.context(),
      previousSelection = context && context.payload.previousSelection,
      index;

    // do nothing if not dragging or no selection was present
    if (!previousSelection || !previousSelection.length) {
      return;
    }

    index = previousSelection.indexOf(oldConnection);

    if (index === -1) {
      return;
    }

    previousSelection.splice(index, 1, newConnection);
  }

  // lifecycle hooks

  this.postExecuted(
    "elements.move",
    function(context) {
      var closure = context.closure,
        allConnections = closure.allConnections;

      forEach(allConnections, fixConnection);
    },
    true
  );

  this.preExecute("connection.reconnect", replaceReconnectedConnection);

  this.postExecuted("element.updateProperties", function(event) {
    var context = event.context,
      properties = context.properties,
      element = context.element,
      businessObject = element.businessObject,
      connection;

    // remove condition on change to default
    if (properties.default) {
      connection = find(
        element.outgoing,
        matchPattern({ id: element.businessObject.default.id })
      );

      if (connection) {
        modeling.updateProperties(connection, {
          conditionExpression: undefined
        });
      }
    }

    // remove default from source on change to conditional
    if (
      properties.conditionExpression &&
      businessObject.sourceRef.default === businessObject
    ) {
      modeling.updateProperties(element.source, { default: undefined });
    }
  });
}

inherits(ReplaceConnectionBehavior, CommandInterceptor);

ReplaceConnectionBehavior.$inject = [
  "eventBus",
  "modeling",
  "bpmnRules",
  "injector"
];
